home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / lib / xulrunner-1.9.1.5 / components / nsSearchSuggestions.js < prev    next >
Text File  |  2009-11-09  |  24KB  |  760 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Google Suggest Autocomplete Implementation for Firefox.
  15.  *
  16.  * The Initial Developer of the Original Code is Google Inc.
  17.  * Portions created by the Initial Developer are Copyright (C) 2006
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Ben Goodger <beng@google.com>
  22.  *   Mike Connor <mconnor@mozilla.com>
  23.  *   Joe Hughes  <joe@retrovirus.com>
  24.  *   Pamela Greene <pamg.bugs@gmail.com>
  25.  *
  26.  * Alternatively, the contents of this file may be used under the terms of
  27.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  28.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29.  * in which case the provisions of the GPL or the LGPL are applicable instead
  30.  * of those above. If you wish to allow use of your version of this file only
  31.  * under the terms of either the GPL or the LGPL, and not to allow others to
  32.  * use your version of this file under the terms of the MPL, indicate your
  33.  * decision by deleting the provisions above and replace them with the notice
  34.  * and other provisions required by the GPL or the LGPL. If you do not delete
  35.  * the provisions above, a recipient may use your version of this file under
  36.  * the terms of any one of the MPL, the GPL or the LGPL.
  37.  *
  38.  * ***** END LICENSE BLOCK ***** */
  39.  
  40. const SEARCH_RESPONSE_SUGGESTION_JSON = "application/x-suggestions+json";
  41.  
  42. const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
  43. const XPCOM_SHUTDOWN_TOPIC              = "xpcom-shutdown";
  44. const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
  45. const SEARCH_BUNDLE = "chrome://global/locale/search/search.properties";
  46.  
  47. const Cc = Components.classes;
  48. const Ci = Components.interfaces;
  49. const Cr = Components.results;
  50. const Cu = Components.utils;
  51.  
  52. const HTTP_OK                    = 200;
  53. const HTTP_INTERNAL_SERVER_ERROR = 500;
  54. const HTTP_BAD_GATEWAY           = 502;
  55. const HTTP_SERVICE_UNAVAILABLE   = 503;
  56.  
  57. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  58.  
  59. /**
  60.  * SuggestAutoCompleteResult contains the results returned by the Suggest
  61.  * service - it implements nsIAutoCompleteResult and is used by the auto-
  62.  * complete controller to populate the front end.
  63.  * @constructor
  64.  */
  65. function SuggestAutoCompleteResult(searchString,
  66.                                    searchResult,
  67.                                    defaultIndex,
  68.                                    errorDescription,
  69.                                    results,
  70.                                    comments,
  71.                                    formHistoryResult) {
  72.   this._searchString = searchString;
  73.   this._searchResult = searchResult;
  74.   this._defaultIndex = defaultIndex;
  75.   this._errorDescription = errorDescription;
  76.   this._results = results;
  77.   this._comments = comments;
  78.   this._formHistoryResult = formHistoryResult;
  79. }
  80. SuggestAutoCompleteResult.prototype = {
  81.   /**
  82.    * The user's query string
  83.    * @private
  84.    */
  85.   _searchString: "",
  86.  
  87.   /**
  88.    * The result code of this result object, see |get searchResult| for possible
  89.    * values.
  90.    * @private
  91.    */
  92.   _searchResult: 0,
  93.  
  94.   /**
  95.    * The default item that should be entered if none is selected
  96.    * @private
  97.    */
  98.   _defaultIndex: 0,
  99.  
  100.   /**
  101.    * The reason the search failed
  102.    * @private
  103.    */
  104.   _errorDescription: "",
  105.  
  106.   /**
  107.    * The list of words returned by the Suggest Service
  108.    * @private
  109.    */
  110.   _results: [],
  111.  
  112.   /**
  113.    * The list of Comments (number of results - or page titles) returned by the
  114.    * Suggest Service.
  115.    * @private
  116.    */
  117.   _comments: [],
  118.  
  119.   /**
  120.    * A reference to the form history nsIAutocompleteResult that we're wrapping.
  121.    * We use this to forward removeEntryAt calls as needed.
  122.    */
  123.   _formHistoryResult: null,
  124.  
  125.   /**
  126.    * @return the user's query string
  127.    */
  128.   get searchString() {
  129.     return this._searchString;
  130.   },
  131.  
  132.   /**
  133.    * @return the result code of this result object, either:
  134.    *         RESULT_IGNORED   (invalid searchString)
  135.    *         RESULT_FAILURE   (failure)
  136.    *         RESULT_NOMATCH   (no matches found)
  137.    *         RESULT_SUCCESS   (matches found)
  138.    */
  139.   get searchResult() {
  140.     return this._searchResult;
  141.   },
  142.  
  143.   /**
  144.    * @return the default item that should be entered if none is selected
  145.    */
  146.   get defaultIndex() {
  147.     return this._defaultIndex;
  148.   },
  149.  
  150.   /**
  151.    * @return the reason the search failed
  152.    */
  153.   get errorDescription() {
  154.     return this._errorDescription;
  155.   },
  156.  
  157.   /**
  158.    * @return the number of results
  159.    */
  160.   get matchCount() {
  161.     return this._results.length;
  162.   },
  163.  
  164.   /**
  165.    * Retrieves a result
  166.    * @param  index    the index of the result requested
  167.    * @return          the result at the specified index
  168.    */
  169.   getValueAt: function(index) {
  170.     return this._results[index];
  171.   },
  172.  
  173.   /**
  174.    * Retrieves a comment (metadata instance)
  175.    * @param  index    the index of the comment requested
  176.    * @return          the comment at the specified index
  177.    */
  178.   getCommentAt: function(index) {
  179.     return this._comments[index];
  180.   },
  181.  
  182.   /**
  183.    * Retrieves a style hint specific to a particular index.
  184.    * @param  index    the index of the style hint requested
  185.    * @return          the style hint at the specified index
  186.    */
  187.   getStyleAt: function(index) {
  188.     if (!this._comments[index])
  189.       return null;  // not a category label, so no special styling
  190.  
  191.     if (index == 0)
  192.       return "suggestfirst";  // category label on first line of results
  193.  
  194.     return "suggesthint";   // category label on any other line of results
  195.   },
  196.  
  197.   /**
  198.    * Retrieves an image url.
  199.    * @param  index    the index of the image url requested
  200.    * @return          the image url at the specified index
  201.    */
  202.   getImageAt: function(index) {
  203.     return "";
  204.   },
  205.  
  206.   /**
  207.    * Removes a result from the resultset
  208.    * @param  index    the index of the result to remove
  209.    */
  210.   removeValueAt: function(index, removeFromDatabase) {
  211.     // Forward the removeValueAt call to the underlying result if we have one
  212.     // Note: this assumes that the form history results were added to the top
  213.     // of our arrays.
  214.     if (removeFromDatabase && this._formHistoryResult &&
  215.         index < this._formHistoryResult.matchCount) {
  216.       // Delete the history result from the DB
  217.       this._formHistoryResult.removeValueAt(index, true);
  218.     }
  219.     this._results.splice(index, 1);
  220.     this._comments.splice(index, 1);
  221.   },
  222.  
  223.   // nsISupports
  224.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult])
  225. };
  226.  
  227. /**
  228.  * SuggestAutoComplete is a base class that implements nsIAutoCompleteSearch
  229.  * and can collect results for a given search by using the search URL supplied
  230.  * by the subclass. We do it this way since the AutoCompleteController in
  231.  * Mozilla requires a unique XPCOM Service for every search provider, even if
  232.  * the logic for two providers is identical.
  233.  * @constructor
  234.  */
  235. function SuggestAutoComplete() {
  236.   this._init();
  237. }
  238. SuggestAutoComplete.prototype = {
  239.  
  240.   _init: function() {
  241.     this._addObservers();
  242.     this._loadSuggestPref();
  243.   },
  244.  
  245.   /**
  246.    * this._strings is the string bundle for message internationalization.
  247.    */
  248.   get _strings() {
  249.     if (!this.__strings) {
  250.       var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  251.                 getService(Ci.nsIStringBundleService);
  252.  
  253.       this.__strings = sbs.createBundle(SEARCH_BUNDLE);
  254.     }
  255.     return this.__strings;
  256.   },
  257.   __strings: null,
  258.  
  259.   /**
  260.    * Search suggestions will be shown if this._suggestEnabled is true.
  261.    */
  262.   _loadSuggestPref: function SAC_loadSuggestPref() {
  263.     var prefService = Cc["@mozilla.org/preferences-service;1"].
  264.                       getService(Ci.nsIPrefBranch);
  265.     this._suggestEnabled = prefService.getBoolPref(BROWSER_SUGGEST_PREF);
  266.   },
  267.   _suggestEnabled: null,
  268.  
  269.   /*************************************************************************
  270.    * Server request backoff implementation fields below
  271.    * These allow us to throttle requests if the server is getting hammered.
  272.    **************************************************************************/
  273.  
  274.   /**
  275.    * This is an array that contains the timestamps (in unixtime) of
  276.    * the last few backoff-triggering errors.
  277.    */
  278.   _serverErrorLog: [],
  279.  
  280.   /**
  281.    * If we receive this number of backoff errors within the amount of time
  282.    * specified by _serverErrorPeriod, then we initiate backoff.
  283.    */
  284.   _maxErrorsBeforeBackoff: 3,
  285.  
  286.   /**
  287.    * If we receive enough consecutive errors (where "enough" is defined by
  288.    * _maxErrorsBeforeBackoff above) within this time period,
  289.    * we trigger the backoff behavior.
  290.    */
  291.   _serverErrorPeriod: 600000,  // 10 minutes in milliseconds
  292.  
  293.   /**
  294.    * If we get another backoff error immediately after timeout, we increase the
  295.    * backoff to (2 x old period) + this value.
  296.    */
  297.   _serverErrorTimeoutIncrement: 600000,  // 10 minutes in milliseconds
  298.  
  299.   /**
  300.    * The current amount of time to wait before trying a server request
  301.    * after receiving a backoff error.
  302.    */
  303.   _serverErrorTimeout: 0,
  304.  
  305.   /**
  306.    * Time (in unixtime) after which we're allowed to try requesting again.
  307.    */
  308.   _nextRequestTime: 0,
  309.  
  310.   /**
  311.    * The last engine we requested against (so that we can tell if the
  312.    * user switched engines).
  313.    */
  314.   _serverErrorEngine: null,
  315.  
  316.   /**
  317.    * The XMLHttpRequest object.
  318.    * @private
  319.    */
  320.   _request: null,
  321.  
  322.   /**
  323.    * The object implementing nsIAutoCompleteObserver that we notify when
  324.    * we have found results
  325.    * @private
  326.    */
  327.   _listener: null,
  328.  
  329.   /**
  330.    * If this is true, we'll integrate form history results with the
  331.    * suggest results.
  332.    */
  333.   _includeFormHistory: true,
  334.  
  335.   /**
  336.    * True if a request for remote suggestions was sent. This is used to
  337.    * differentiate between the "_request is null because the request has
  338.    * already returned a result" and "_request is null because no request was
  339.    * sent" cases.
  340.    */
  341.   _sentSuggestRequest: false,
  342.  
  343.   /**
  344.    * This is the callback for the suggest timeout timer.
  345.    */
  346.   notify: function SAC_notify(timer) {
  347.     // FIXME: bug 387341
  348.     // Need to break the cycle between us and the timer.
  349.     this._formHistoryTimer = null;
  350.  
  351.     // If this._listener is null, we've already sent out suggest results, so
  352.     // nothing left to do here.
  353.     if (!this._listener)
  354.       return;
  355.  
  356.     // Otherwise, the XMLHTTPRequest for suggest results is taking too long,
  357.     // so send out the form history results and cancel the request.
  358.     this._listener.onSearchResult(this, this._formHistoryResult);
  359.     this._reset();
  360.   },
  361.  
  362.   /**
  363.    * This determines how long (in ms) we should wait before giving up on
  364.    * the suggestions and just showing local form history results.
  365.    */
  366.   _suggestionTimeout: 500,
  367.  
  368.   /**
  369.    * This is the callback for that the form history service uses to
  370.    * send us results.
  371.    */
  372.   onSearchResult: function SAC_onSearchResult(search, result) {
  373.     this._formHistoryResult = result;
  374.  
  375.     if (this._request) {
  376.       // We still have a pending request, wait a bit to give it a chance to
  377.       // finish.
  378.       this._formHistoryTimer = Cc["@mozilla.org/timer;1"].
  379.                                createInstance(Ci.nsITimer);
  380.       this._formHistoryTimer.initWithCallback(this, this._suggestionTimeout,
  381.                                               Ci.nsITimer.TYPE_ONE_SHOT);
  382.     } else if (!this._sentSuggestRequest) {
  383.       // We didn't send a request, so just send back the form history results.
  384.       this._listener.onSearchResult(this, this._formHistoryResult);
  385.       this._reset();
  386.     }
  387.   },
  388.  
  389.   /**
  390.    * This is the URI that the last suggest request was sent to.
  391.    */
  392.   _suggestURI: null,
  393.  
  394.   /**
  395.    * Autocomplete results from the form history service get stored here.
  396.    */
  397.   _formHistoryResult: null,
  398.  
  399.   /**
  400.    * This holds the suggest server timeout timer, if applicable.
  401.    */
  402.   _formHistoryTimer: null,
  403.  
  404.   /**
  405.    * This clears all the per-request state.
  406.    */
  407.   _reset: function SAC_reset() {
  408.     // Don't let go of our listener and form history result if the timer is
  409.     // still pending, the timer will call _reset() when it fires.
  410.     if (!this._formHistoryTimer) {
  411.       this._listener = null;
  412.       this._formHistoryResult = null;
  413.     }
  414.     this._request = null;
  415.   },
  416.  
  417.   /**
  418.    * This sends an autocompletion request to the form history service,
  419.    * which will call onSearchResults with the results of the query.
  420.    */
  421.   _startHistorySearch: function SAC_SHSearch(searchString, searchParam, previousResult) {
  422.     var formHistory =
  423.       Cc["@mozilla.org/autocomplete/search;1?name=form-history"].
  424.       createInstance(Ci.nsIAutoCompleteSearch);
  425.     formHistory.startSearch(searchString, searchParam, previousResult, this);
  426.   },
  427.  
  428.   /**
  429.    * Makes a note of the fact that we've received a backoff-triggering
  430.    * response, so that we can adjust the backoff behavior appropriately.
  431.    */
  432.   _noteServerError: function SAC__noteServeError() {
  433.     var currentTime = Date.now();
  434.  
  435.     this._serverErrorLog.push(currentTime);
  436.     if (this._serverErrorLog.length > this._maxErrorsBeforeBackoff)
  437.       this._serverErrorLog.shift();
  438.  
  439.     if ((this._serverErrorLog.length == this._maxErrorsBeforeBackoff) &&
  440.         ((currentTime - this._serverErrorLog[0]) < this._serverErrorPeriod)) {
  441.       // increase timeout, and then don't request until timeout is over
  442.       this._serverErrorTimeout = (this._serverErrorTimeout * 2) +
  443.                                  this._serverErrorTimeoutIncrement;
  444.       this._nextRequestTime = currentTime + this._serverErrorTimeout;
  445.     }
  446.   },
  447.  
  448.   /**
  449.    * Resets the backoff behavior; called when we get a successful response.
  450.    */
  451.   _clearServerErrors: function SAC__clearServerErrors() {
  452.     this._serverErrorLog = [];
  453.     this._serverErrorTimeout = 0;
  454.     this._nextRequestTime = 0;
  455.   },
  456.  
  457.   /**
  458.    * This checks whether we should send a server request (i.e. we're not
  459.    * in a error-triggered backoff period.
  460.    *
  461.    * @private
  462.    */
  463.   _okToRequest: function SAC__okToRequest() {
  464.     return Date.now() > this._nextRequestTime;
  465.   },
  466.  
  467.   /**
  468.    * This checks to see if the new search engine is different
  469.    * from the previous one, and if so clears any error state that might
  470.    * have accumulated for the old engine.
  471.    *
  472.    * @param engine The engine that the suggestion request would be sent to.
  473.    * @private
  474.    */
  475.   _checkForEngineSwitch: function SAC__checkForEngineSwitch(engine) {
  476.     if (engine == this._serverErrorEngine)
  477.       return;
  478.  
  479.     // must've switched search providers, clear old errors
  480.     this._serverErrorEngine = engine;
  481.     this._clearServerErrors();
  482.   },
  483.  
  484.   /**
  485.    * This returns true if the status code of the HTTP response
  486.    * represents a backoff-triggering error.
  487.    *
  488.    * @param status  The status code from the HTTP response
  489.    * @private
  490.    */
  491.   _isBackoffError: function SAC__isBackoffError(status) {
  492.     return ((status == HTTP_INTERNAL_SERVER_ERROR) ||
  493.             (status == HTTP_BAD_GATEWAY) ||
  494.             (status == HTTP_SERVICE_UNAVAILABLE));
  495.   },
  496.  
  497.   /**
  498.    * Called when the 'readyState' of the XMLHttpRequest changes. We only care
  499.    * about state 4 (COMPLETED) - handle the response data.
  500.    * @private
  501.    */
  502.   onReadyStateChange: function() {
  503.     // xxx use the real const here
  504.     if (!this._request || this._request.readyState != 4)
  505.       return;
  506.  
  507.     try {
  508.       var status = this._request.status;
  509.     } catch (e) {
  510.       // The XML HttpRequest can throw NS_ERROR_NOT_AVAILABLE.
  511.       return;
  512.     }
  513.  
  514.     if (this._isBackoffError(status)) {
  515.       this._noteServerError();
  516.       return;
  517.     }
  518.  
  519.     var responseText = this._request.responseText;
  520.     if (status != HTTP_OK || responseText == "")
  521.       return;
  522.  
  523.     this._clearServerErrors();
  524.  
  525.     var serverResults = JSON.parse(responseText);
  526.     var searchString = serverResults[0] || "";
  527.     var results = serverResults[1] || [];
  528.  
  529.     var comments = [];  // "comments" column values for suggestions
  530.     var historyResults = [];
  531.     var historyComments = [];
  532.  
  533.     // If form history is enabled and has results, add them to the list.
  534.     if (this._includeFormHistory && this._formHistoryResult &&
  535.         (this._formHistoryResult.searchResult ==
  536.          Ci.nsIAutoCompleteResult.RESULT_SUCCESS)) {
  537.       for (var i = 0; i < this._formHistoryResult.matchCount; ++i) {
  538.         var term = this._formHistoryResult.getValueAt(i);
  539.  
  540.         // we don't want things to appear in both history and suggestions
  541.         var dupIndex = results.indexOf(term);
  542.         if (dupIndex != -1)
  543.           results.splice(dupIndex, 1);
  544.  
  545.         historyResults.push(term);
  546.         historyComments.push("");
  547.       }
  548.     }
  549.  
  550.     // fill out the comment column for the suggestions
  551.     for (var i = 0; i < results.length; ++i)
  552.       comments.push("");
  553.  
  554.     // if we have any suggestions, put a label at the top
  555.     if (comments.length > 0)
  556.       comments[0] = this._strings.GetStringFromName("suggestion_label");
  557.  
  558.     // now put the history results above the suggestions
  559.     var finalResults = historyResults.concat(results);
  560.     var finalComments = historyComments.concat(comments);
  561.  
  562.     // Notify the FE of our new results
  563.     this.onResultsReady(searchString, finalResults, finalComments,
  564.                         this._formHistoryResult);
  565.  
  566.     // Reset our state for next time.
  567.     this._reset();
  568.   },
  569.  
  570.   /**
  571.    * Notifies the front end of new results.
  572.    * @param searchString  the user's query string
  573.    * @param results       an array of results to the search
  574.    * @param comments      an array of metadata corresponding to the results
  575.    * @private
  576.    */
  577.   onResultsReady: function(searchString, results, comments,
  578.                            formHistoryResult) {
  579.     if (this._listener) {
  580.       var result = new SuggestAutoCompleteResult(
  581.           searchString,
  582.           Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
  583.           0,
  584.           "",
  585.           results,
  586.           comments,
  587.           formHistoryResult);
  588.  
  589.       this._listener.onSearchResult(this, result);
  590.  
  591.       // Null out listener to make sure we don't notify it twice, in case our
  592.       // timer callback still hasn't run.
  593.       this._listener = null;
  594.     }
  595.   },
  596.  
  597.   /**
  598.    * Initiates the search result gathering process. Part of
  599.    * nsIAutoCompleteSearch implementation.
  600.    *
  601.    * @param searchString    the user's query string
  602.    * @param searchParam     unused, "an extra parameter"; even though
  603.    *                        this parameter and the next are unused, pass
  604.    *                        them through in case the form history
  605.    *                        service wants them
  606.    * @param previousResult  unused, a client-cached store of the previous
  607.    *                        generated resultset for faster searching.
  608.    * @param listener        object implementing nsIAutoCompleteObserver which
  609.    *                        we notify when results are ready.
  610.    */
  611.   startSearch: function(searchString, searchParam, previousResult, listener) {
  612.     var searchService = Cc["@mozilla.org/browser/search-service;1"].
  613.                         getService(Ci.nsIBrowserSearchService);
  614.  
  615.     // If there's an existing request, stop it. There is no smart filtering
  616.     // here as there is when looking through history/form data because the
  617.     // result set returned by the server is different for every typed value -
  618.     // "ocean breathes" does not return a subset of the results returned for
  619.     // "ocean", for example. This does nothing if there is no current request.
  620.     this.stopSearch();
  621.  
  622.     this._listener = listener;
  623.  
  624.     var engine = searchService.currentEngine;
  625.  
  626.     this._checkForEngineSwitch(engine);
  627.  
  628.     if (!searchString ||
  629.         !this._suggestEnabled ||
  630.         !engine.supportsResponseType(SEARCH_RESPONSE_SUGGESTION_JSON) ||
  631.         !this._okToRequest()) {
  632.       // We have an empty search string (user pressed down arrow to see
  633.       // history), or search suggestions are disabled, or the current engine
  634.       // has no suggest functionality, or we're in backoff mode; so just use
  635.       // local history.
  636.       this._sentSuggestRequest = false;
  637.       this._startHistorySearch(searchString, searchParam, previousResult);
  638.       return;
  639.     }
  640.  
  641.     // Actually do the search
  642.     this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
  643.                     createInstance(Ci.nsIXMLHttpRequest);
  644.     var submission = engine.getSubmission(searchString,
  645.                                           SEARCH_RESPONSE_SUGGESTION_JSON);
  646.     this._suggestURI = submission.uri;
  647.     var method = (submission.postData ? "POST" : "GET");
  648.     this._request.open(method, this._suggestURI.spec, true);
  649.     this._request.channel.notificationCallbacks = new SearchSuggestLoadListener();
  650.  
  651.     var self = this;
  652.     function onReadyStateChange() {
  653.       self.onReadyStateChange();
  654.     }
  655.     this._request.onreadystatechange = onReadyStateChange;
  656.     this._request.send(submission.postData);
  657.  
  658.     if (this._includeFormHistory) {
  659.       this._sentSuggestRequest = true;
  660.       this._startHistorySearch(searchString, searchParam, previousResult);
  661.     }
  662.   },
  663.  
  664.   /**
  665.    * Ends the search result gathering process. Part of nsIAutoCompleteSearch
  666.    * implementation.
  667.    */
  668.   stopSearch: function() {
  669.     if (this._request) {
  670.       this._request.abort();
  671.       this._reset();
  672.     }
  673.   },
  674.  
  675.   /**
  676.    * nsIObserver
  677.    */
  678.   observe: function SAC_observe(aSubject, aTopic, aData) {
  679.     switch (aTopic) {
  680.       case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
  681.         this._loadSuggestPref();
  682.         break;
  683.       case XPCOM_SHUTDOWN_TOPIC:
  684.         this._removeObservers();
  685.         break;
  686.     }
  687.   },
  688.  
  689.   _addObservers: function SAC_addObservers() {
  690.     var prefService2 = Cc["@mozilla.org/preferences-service;1"].
  691.                        getService(Ci.nsIPrefBranch2);
  692.     prefService2.addObserver(BROWSER_SUGGEST_PREF, this, false);
  693.  
  694.     var os = Cc["@mozilla.org/observer-service;1"].
  695.              getService(Ci.nsIObserverService);
  696.     os.addObserver(this, XPCOM_SHUTDOWN_TOPIC, false);
  697.   },
  698.  
  699.   _removeObservers: function SAC_removeObservers() {
  700.     var prefService2 = Cc["@mozilla.org/preferences-service;1"].
  701.                        getService(Ci.nsIPrefBranch2);
  702.     prefService2.removeObserver(BROWSER_SUGGEST_PREF, this);
  703.  
  704.     var os = Cc["@mozilla.org/observer-service;1"].
  705.              getService(Ci.nsIObserverService);
  706.     os.removeObserver(this, XPCOM_SHUTDOWN_TOPIC);
  707.   },
  708.  
  709.   // nsISupports
  710.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteSearch,
  711.                                          Ci.nsIAutoCompleteObserver])
  712. };
  713.  
  714. function SearchSuggestLoadListener() {
  715. }
  716. SearchSuggestLoadListener.prototype = {
  717.   // nsIBadCertListener2
  718.   notifyCertProblem: function SSLL_certProblem(socketInfo, status, targetSite) {
  719.     return true;
  720.   },
  721.  
  722.   // nsISSLErrorListener
  723.   notifySSLError: function SSLL_SSLError(socketInfo, error, targetSite) {
  724.     return true;
  725.   },
  726.  
  727.   // nsIInterfaceRequestor
  728.   getInterface: function SSLL_getInterface(iid) {
  729.     return this.QueryInterface(iid);
  730.   },
  731.  
  732.   // nsISupports
  733.   QueryInterface: XPCOMUtils.generateQI([Ci.nsIBadCertListener2,
  734.                                          Ci.nsISSLErrorListener,
  735.                                          Ci.nsIInterfaceRequestor])
  736. };
  737.  
  738. /**
  739.  * SearchSuggestAutoComplete is a service implementation that handles suggest
  740.  * results specific to web searches.
  741.  * @constructor
  742.  */
  743. function SearchSuggestAutoComplete() {
  744.   // This calls _init() in the parent class (SuggestAutoComplete) via the
  745.   // prototype, below.
  746.   this._init();
  747. }
  748. SearchSuggestAutoComplete.prototype = {
  749.   classDescription: "Remote Search Suggestions",
  750.   contractID: "@mozilla.org/autocomplete/search;1?name=search-autocomplete",
  751.   classID: Components.ID("{aa892eb4-ffbf-477d-9f9a-06c995ae9f27}"),
  752.   __proto__: SuggestAutoComplete.prototype,
  753.   serviceURL: ""
  754. };
  755.  
  756. var component = [SearchSuggestAutoComplete];
  757. function NSGetModule(compMgr, fileSpec) {
  758.   return XPCOMUtils.generateModule(component);
  759. }
  760.